#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import serial
import time
import threading
import struct
import csv
import os
import traceback
from flask import Flask, jsonify, request, send_file
from flask_cors import CORS
from datetime import datetime

app = Flask(__name__)
CORS(app)

# 全局状态管理
class AppState:
    def __init__(self):
        self.running = False
        self.measuring = False
        self.data = []
        self.current_angle = None
        self.current_distance = None
        self.system_status = "未连接"
        self.serial_port = None
        self.stepper = None
        self.motor = None
        self.sensor_50 = None
        self.sensor_100 = None
        self.current_sensor = None
        self.collector = None
        self.csv_file = None
        self.total_move_distance = 0
        self.lead = 5
        self.config = {
            "port": "COM9",
            "baudrate": 115200,
            "motor_id": 0x01,
            "sensor_50_address": 3,
            "sensor_100_address": 4,
            "stepper_id": 2,
            "repeat_times": 50,
            "stepper_cycles": 2,
            "sample_interval": 0.05,
            "measure_mode": "continuous",
            "angle_step": 10,
            "switch_mode": "value",
            "photo_address": 6
        }
        self.lock = threading.Lock()
        self.photo_switch = {
            "positive_limit": False,
            "origin": False,
            "negative_limit": False
        }

state = AppState()

# ---------- PhotoelectricSwitchController（复用共享串口） ----------
class PhotoelectricSwitchController:
    def __init__(self, serial_port, slave_address=6, lock=None, input_register_base=0x0000):
        if serial_port is None:
            raise ValueError("必须提供共享 serial_port 实例")
        self.ser = serial_port
        self.slave = int(slave_address)
        self.lock = lock or threading.Lock()
        self.input_register_base = input_register_base
        self.switch_channels = {
            "positive_limit": 1,
            "origin": 2,
            "negative_limit": 3
        }

    def _crc16(self, data: bytes) -> bytes:
        crc = 0xFFFF
        for b in data:
            crc ^= b
            for _ in range(8):
                if crc & 0x0001:
                    crc >>= 1
                    crc ^= 0xA001
                else:
                    crc >>= 1
        return struct.pack('<H', crc)

    def _build_read_input_register_request(self, register_addr, quantity=1):
        req = bytearray()
        req.append(self.slave)
        req.append(0x04)
        req.extend(register_addr.to_bytes(2, byteorder='big'))
        req.extend(quantity.to_bytes(2, byteorder='big'))
        req.extend(self._crc16(bytes(req)))
        return bytes(req)

    def _send_request_and_read(self, req, expected_min_len=7, timeout=1.0):
        with self.lock:
            try:
                try:
                    self.ser.reset_input_buffer()
                    self.ser.reset_output_buffer()
                except Exception:
                    pass
                self.ser.write(req)
                self.ser.flush()
                t0 = time.time()
                resp = bytearray()
                while time.time() - t0 < timeout and len(resp) < expected_min_len:
                    chunk = self.ser.read(expected_min_len - len(resp))
                    if chunk:
                        resp.extend(chunk)
                    else:
                        time.sleep(0.01)
                if len(resp) >= 3:
                    bytecount = resp[2]
                    total_len = 3 + bytecount + 2
                    while time.time() - t0 < timeout and len(resp) < total_len:
                        chunk = self.ser.read(total_len - len(resp))
                        if chunk:
                            resp.extend(chunk)
                        else:
                            time.sleep(0.01)
                if len(resp) < expected_min_len:
                    return None
                if len(resp) >= 2:
                    recv_crc = resp[-2:]
                    calc_crc = self._crc16(bytes(resp[:-2]))
                    if recv_crc != calc_crc:
                        print(f"[Photo] CRC 校验失败: recv={recv_crc.hex()} calc={calc_crc.hex()} resp={resp.hex()}")
                        return None
                return bytes(resp)
            except Exception as e:
                print(f"[Photo] 发送/读取失败: {e}")
                return None

    def read_switch_state(self, channel, timeout=0.8):
        try:
            reg = self.input_register_base + (int(channel) - 1)
            req = self._build_read_input_register_request(reg, quantity=1)
            resp = self._send_request_and_read(req, expected_min_len=7, timeout=timeout)
            if not resp or len(resp) < 7:
                return False
            hi = resp[3]
            lo = resp[4]
            val = (hi << 8) | lo
            return val == 1
        except Exception as e:
            print(f"[Photo] 通道{channel}读取异常: {e}")
            return False

    def update_all_switches(self):
        for switch_name, channel in self.switch_channels.items():
            state.photo_switch[switch_name] = self.read_switch_state(channel)
        return state.photo_switch

    def wait_for_switch(self, switch_name, target_state=True, timeout=30):
        start_time = time.time()
        while time.time() - start_time < timeout and state.running:
            self.update_all_switches()
            if state.photo_switch.get(switch_name) == target_state:
                return True
            time.sleep(0.08)
        return False

# ---------- StepperMotor（基于 PR 分段移动） ----------
class StepperMotor:
    """
    基于控制器 PR 指令实现分段移动（兼顾长距离和限位检测）
    依赖：
      - 写单寄存器 FC=0x06 来设置 PR 模式/速度/触发/急停等
      - PR0 高位寄存器: 0x6201，低位: 0x6202（示例）
      - 触发寄存器: 0x6002 写 0x0010 触发 PR0 运行，写 0x0040 急停
    """
    def __init__(self, serial_port, motor_id=1, lock=None, photo_controller=None):
        self.ser = serial_port  # 串口对象
        self.motor_id = int(motor_id)  # 电机地址
        self.lock = lock or threading.Lock()  # 线程锁
        self.photo_controller = photo_controller  # 光电开关控制器
        self.steps_per_mm = 200  # 每毫米步数，按实际调整

    def _crc16(self, data: bytes) -> bytes:
        # 计算CRC16校验码
        crc = 0xFFFF
        for b in data:
            crc ^= b
            for _ in range(8):
                if crc & 0x0001:
                    crc >>= 1
                    crc ^= 0xA001
                else:
                    crc >>= 1
        return struct.pack('<H', crc)

    def _write_register(self, register: int, value: int, timeout=0.2) -> bool:
    # 写单个寄存器
        with self.lock:  # 加锁，保证多线程安全
            try:
                cmd = bytearray()  # 创建命令字节数组
                cmd.append(self.motor_id & 0xFF)  # 添加电机地址
                cmd.append(0x06)  # 功能码FC06（写单寄存器）
                cmd.extend(register.to_bytes(2, byteorder='big'))  # 添加寄存器地址（2字节，大端）
                cmd.extend((int(value) & 0xFFFF).to_bytes(2, byteorder='big'))  # 添加写入值（2字节，大端）
                cmd.extend(self._crc16(bytes(cmd)))  # 添加CRC16校验码
                try:
                    self.ser.reset_input_buffer()  # 清空串口输入缓冲区
                    self.ser.reset_output_buffer()  # 清空串口输出缓冲区
                except Exception:
                    pass
                self.ser.write(bytes(cmd))  # 发送命令到串口
                self.ser.flush()  # 强制刷新缓冲区
                time.sleep(0.02)  # 等待硬件响应
                resp = self.ser.read(8)  # 读取8字节响应
                if len(resp) == 8:
                    return True  # 写入成功
                else:
                    # 打印调试信息
                    print(f"[Stepper] 写寄存器未返回预期长度: reg=0x{register:04X} val=0x{value:04X} resp={resp.hex() if resp else 'None'}")
                    return False  # 响应长度不对，写入失败
            except Exception as e:
                print(f"[Stepper] 写寄存器失败 reg=0x{register:04X} val=0x{value:04X} -> {e}")  # 打印异常信息
                return False  # 写入失败

    def _write_pr32(self, signed_value: int) -> bool:
        """
        将 signed_value 写入 PR0 高/低寄存器（0x6201/0x6202）
        按示例先写高位寄存器0x6201再写低位0x6202
        """
        val32 = signed_value & 0xFFFFFFFF
        high = (val32 >> 16) & 0xFFFF
        low = val32 & 0xFFFF
        reg_high = 0x6201
        reg_low = 0x6202
        ok1 = self._write_register(reg_high, high)
        if not ok1:
            return False
        ok2 = self._write_register(reg_low, low)
        return ok2

    def emergency_stop(self) -> bool:
        # 步进电机急停
        return self._write_register(0x6002, 0x0040)

    def _set_pr_mode_relative(self) -> bool:
        # 设置PR模式为相对模式
        return self._write_register(0x6200, 0x0041)

    def _set_pr_mode_absolute(self) -> bool:
        # 设置PR模式为绝对模式
        return self._write_register(0x6200, 0x0001)

    def _set_pr_speed(self, speed_value: int) -> bool:
        # 设置PR运行速度
        return self._write_register(0x6203, int(speed_value) & 0xFFFF)

    def _trigger_pr0_run(self) -> bool:
        # 触发PR0运行
        return self._write_register(0x6002, 0x0010)

    def move_distance(self, distance_mm: float, direction: str,
                      segment_mm: float = 50.0, segment_timeout_per_mm: float = 0.02,
                      pr_speed: int = 0x012C) -> bool:
        """
        步进电机分段移动指定距离，支持限位检测
        distance_mm: 总移动距离（mm）
        direction: 'forward' 或 'backward'
        segment_mm: 每段移动距离（mm）
        segment_timeout_per_mm: 每mm最大超时时间
        pr_speed: PR运行速度
        """
        try:
            if distance_mm <= 0:
                return True

            total_counts = int(round(distance_mm * self.steps_per_mm))
            if direction == 'backward':
                total_counts = -abs(total_counts)
            else:
                total_counts = abs(total_counts)

            seg_mm = float(segment_mm)
            seg_counts_unit = int(round(seg_mm * self.steps_per_mm))
            if seg_counts_unit <= 0:
                seg_counts_unit = max(1, int(round(self.steps_per_mm)))

            remaining = total_counts
            while remaining != 0 and state.running:
                if abs(remaining) > seg_counts_unit:
                    this_counts = seg_counts_unit if remaining > 0 else -seg_counts_unit
                else:
                    this_counts = remaining

                if not self._set_pr_mode_relative():
                    print("[Stepper] 设置 PR 模式失败")
                    return False

                if not self._write_pr32(this_counts):
                    print(f"[Stepper] 写 PR0 位置失败 counts={this_counts}")
                    return False

                if pr_speed is not None:
                    if not self._set_pr_speed(pr_speed):
                        print("[Stepper] 写 PR0 速度失败")

                if not self._trigger_pr0_run():
                    print("[Stepper] 触发 PR0 运行失败")
                    return False

                est_mm = abs(this_counts) / float(self.steps_per_mm)
                seg_timeout = max(1.0, est_mm * segment_timeout_per_mm)
                start_t = time.time()
                while time.time() - start_t < seg_timeout and state.running:
                    if self.photo_controller:
                        try:
                            self.photo_controller.update_all_switches()
                        except Exception:
                            pass
                        # 正限位检测
                        if this_counts > 0 and state.photo_switch.get("positive_limit", False):
                            print("[Stepper] 触发正限位，停止")
                            self.emergency_stop()
                            return False
                        # 负限位检测
                        if this_counts < 0 and state.photo_switch.get("negative_limit", False):
                            print("[Stepper] 触发负限位，停止")
                            self.emergency_stop()
                            return False
                    time.sleep(0.05)

                remaining -= this_counts
                time.sleep(0.05)

            return True
        except Exception as e:
            print(f"[Stepper] 分段移动异常: {e}")
            return False

    def close(self):
        # 关闭步进电机（预留）
        pass

# ---------- HaitaiMotor（云台） ----------
class HaitaiMotor:
    def __init__(self, serial_port, motor_id=0x01, lock=None):
        if serial_port is None:
            raise ValueError("必须提供共享 serial_port 实例")
        self.ser = serial_port
        self.motor_id = motor_id
        self.lock = lock or threading.Lock()
        self.counts_per_circle = 16384
        self.packet_seq = 0x00

    def _crc16_modbus(self, data):
        crc = 0xFFFF
        for b in data:
            crc ^= b
            for _ in range(8):
                if crc & 0x0001:
                    crc >>= 1
                    crc ^= 0xA001
                else:
                    crc >>= 1
        return crc & 0xFFFF

    def _pack_frame(self, cmd, data_bytes=b''):
        frame_header = [0x3E, self.packet_seq, self.motor_id, cmd, len(data_bytes)]
        frame_header_bytes = bytes(frame_header)
        crc = self._crc16_modbus(frame_header_bytes + data_bytes)
        crc_bytes = struct.pack('<H', crc)
        full_frame = frame_header_bytes + data_bytes + crc_bytes
        self.packet_seq = (self.packet_seq + 1) & 0xFF
        return full_frame

    def _send_command(self, cmd, data_bytes=b'', read_len=15, post_delay=0.1):
        full_frame = self._pack_frame(cmd, data_bytes)
        with self.lock:
            try:
                try:
                    self.ser.reset_input_buffer()
                    self.ser.reset_output_buffer()
                except Exception:
                    pass
                self.ser.write(full_frame)
                self.ser.flush()
                time.sleep(post_delay)
                resp = self.ser.read(read_len)
                if resp and len(resp) >= 5 and resp[0] == 0x3C:
                    return resp
                else:
                    print(f"[电机] 应答无效: 长度={len(resp) if resp else 0}, 数据={resp.hex() if resp else '无'}")
                    return None
            except Exception as e:
                print(f"[电机] 发送命令失败: {e}")
                raise

    def get_current_angle(self):
        try:
            resp = self._send_command(0x2F, b'', read_len=15, post_delay=0.1)
        except Exception as e:
            print(f"[电机] 读取角度出错: {e}")
            return None
        if resp and len(resp) >= 15:
            try:
                single_turn_raw = (resp[5] | (resp[6] << 8)) & 0x3FFF
                multi_turn_raw = (resp[7] | (resp[8] << 8) | (resp[9] << 16) | (resp[10] << 24))
                if multi_turn_raw & 0x80000000:
                    multi_turn_raw -= 0x100000000
                multi_turn_angle = multi_turn_raw * (360.0 / self.counts_per_circle)
                single_turn_angle = single_turn_raw * (360.0 / self.counts_per_circle)
                return multi_turn_angle + (single_turn_angle % 360)
            except Exception as e:
                print(f"[电机] 解析角度失败: {e} | 应答数据: {resp.hex()}")
                return None
        else:
            print(f"[电机] 应答长度错误（需>=15字节）: 实际={len(resp) if resp else 0}")
            return None

    def set_origin(self):
        print("设置当前位置为原点...")
        try:
            resp = self._send_command(0x21, b'', read_len=10, post_delay=0.5)
            if resp and len(resp) >= 10:
                result = resp[7]
                if result == 0x01:
                    print("原点设置成功")
                    return True
                else:
                    print(f"原点设置失败，结果码: 0x{result:02X}")
                    return False
            else:
                print(f"[电机] 设置原点应答无效: {resp.hex() if resp else '无响应'}")
                return False
        except Exception as e:
            print(f"[电机] 设置原点失败: {e}")
            return False

    def move_to_absolute_position(self, target_angle, max_speed=1000):
        target_counts = int(target_angle * (self.counts_per_circle / 360.0))
        target_bytes = struct.pack('<I', target_counts)
        speed_bytes = struct.pack('<H', max_speed)
        try:
            self._send_command(0x57, b'\x01' + speed_bytes, post_delay=0.2)
            time.sleep(0.05)
            self._send_command(0x55, target_bytes, post_delay=0.2)
            return True
        except Exception as e:
            print(f"[电机] 绝对值位置控制失败: {e}")
            return False

    def move_to_zero(self, timeout=300):
        print("开始回归零位...")
        if not self.set_origin():
            return False
        if not self.move_to_absolute_position(0, max_speed=500):
            return False
        start_time = time.time()
        while time.time() - start_time < timeout:
            current_angle = self.get_current_angle()
            if current_angle is not None and abs(current_angle) <= 0.1:
                print("已到达零位")
                return True
            time.sleep(0.2)
        print("回零超时")
        return False

    def rotate_360_continuous(self, timeout=300):
        print("开始连续旋转360度...")
        if not self.move_to_absolute_position(360, max_speed=500):
            return False
        start_time = time.time()
        while time.time() - start_time < timeout and state.running:
            current_angle = self.get_current_angle()
            if current_angle is not None:
                normalized_angle = current_angle % 360
                if abs(normalized_angle - 0) <= 0.1:
                    print("已完成360度旋转")
                    return True
            time.sleep(0.2)
        print("旋转超时")
        return False

    def rotate_fixed_angle(self, angle_step, max_speed=50):
        print(f"开始定点角度采集（间隔{angle_step}度）...")
        total_angle = 360
        current_target = 0
        success = True

        while current_target <= total_angle and state.running:
            if not self.move_to_absolute_position(current_target, max_speed):
                print(f"移动到{current_target}度失败")
                success = False
                break
            time.sleep(1.0)
            current_angle = self.get_current_angle()
            distance = state.current_sensor.read_distance() if state.current_sensor else None
            if current_angle is not None and distance is not None:
                normalized_angle = current_angle % 360
                current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
                with state.lock:
                    state.current_angle = normalized_angle
                    state.current_distance = distance
                    state.data.append((current_time, state.total_move_distance, normalized_angle, distance))
                print(f"定点采集 | 角度: {normalized_angle:.2f}° | 距离: {distance:.5f}mm", end='\r', flush=True)
                self._append_fixed_angle_data(current_time, state.total_move_distance, normalized_angle, distance)
            current_target += angle_step

        print("\n定点角度采集完成")
        return success

    def _append_fixed_angle_data(self, current_time, move_distance, angle, distance):
        try:
            file_exists = os.path.isfile(state.csv_file)
            with open(state.csv_file, 'a', newline='', encoding='utf-8') as f:
                writer = csv.writer(f)
                if not file_exists:
                    writer.writerow([
                        "测量时间", "移动距离(mm)", "云台角度(°)",
                        "距离(mm)", "传感器量程", "采集模式"
                    ])
                writer.writerow([
                    current_time, round(move_distance, 1), round(angle, 4),
                    round(distance, 5), get_current_sensor_range(), "定点"
                ])
        except Exception as e:
            print(f"数据保存失败: {e}")

    def read_status(self):
        try:
            resp = self._send_command(0x40, b'', read_len=12, post_delay=0.1)
            if resp and len(resp) >= 12:
                voltage = resp[5] * 0.2
                current = resp[6] * 0.03
                temperature = resp[7] * 0.4
                fault_code = resp[8]
                run_mode = resp[9]
                mode_map = {0: "关闭", 1: "开环", 3: "速度", 5: "位置"}
                print(f"[状态] 电压: {voltage:.1f}V, 电流: {current:.2f}A, 温度: {temperature:.1f}℃, 模式: {mode_map.get(run_mode, '未知')}")
                return True
        except Exception as e:
            print(f"[电机] 读取状态失败: {e}")
        return False

    def close_motor(self):
        try:
            self._send_command(0x50, b'', post_delay=0.2)
            print("电机已关闭")
        except Exception as e:
            print(f"[电机] 关闭失败: {e}")

    def close(self):
        self.close_motor()

# ---------- 传感器 ----------
class LE100485Sensor:
    def __init__(self, serial_port, sensor_address, range_type, lock=None):
        """
        初始化激光位移传感器（适配协议规范）
        :param serial_port: 串口实例
        :param sensor_address: 传感器地址（如0x01）
        :param range_type: 量程类型（50或100，单位mm）
        :param lock: 线程锁（避免多线程冲突）
        """
        self.ser = serial_port
        self.address = int(sensor_address)  # 从站地址（如0x01）
        self.range_type = range_type  # 量程类型
        self.lock = lock or threading.Lock()
        self.resolution = 0.001  # 协议明确分辨率1μm（0.001mm）
        # 协议中读取距离固定使用2个寄存器（003BH地址，读取0002H个寄存器）
        self.REG_ADDR = 0x003B  # 距离数据寄存器地址
        self.REG_COUNT = 0x0002  # 读取寄存器数量（固定2个）

    def _crc16(self, data: bytes) -> bytes:
        """计算Modbus CRC16校验码（小端模式）"""
        crc = 0xFFFF
        for b in data:
            crc ^= b
            for _ in range(8):
                if crc & 0x0001:
                    crc >>= 1
                    crc ^= 0xA001  # 多项式
                else:
                    crc >>= 1
        return struct.pack('<H', crc)  # 小端模式打包

    def read_distance(self):
        """根据协议读取距离值（单位：mm）"""
        try:
            with self.lock:
                # 1. 构建发送命令（协议格式：从站地址+功能码+寄存器地址+寄存器数量+CRC）
                cmd = bytearray()
                cmd.append(self.address)  # 从站地址（如0x01）
                cmd.append(0x03)  # 功能码（读取保持寄存器）
                # 寄存器地址（003BH）：高位在前
                cmd.extend(self.REG_ADDR.to_bytes(2, byteorder='big'))
                # 寄存器数量（0002H）：高位在前
                cmd.extend(self.REG_COUNT.to_bytes(2, byteorder='big'))
                # 添加CRC校验（低8位在前，高8位在后）
                cmd.extend(self._crc16(cmd))

                # 2. 发送命令
                self.ser.reset_input_buffer()
                self.ser.reset_output_buffer()
                self.ser.write(cmd)
                self.ser.flush()
                time.sleep(0.05)  # 等待传感器响应

                # 3. 接收响应（预期长度：9字节，协议规定格式）
                # 响应格式：从站地址(1) + 功能码(1) + 数据长度(1) + 数据(4) + CRC(2)
                expected_len = 9
                resp = self.ser.read(expected_len)

            # 4. 验证响应合法性
            if len(resp) != expected_len:
                print(f"响应长度错误：实际{len(resp)}字节，预期{expected_len}字节")
                return None
            if resp[0] != self.address:
                print(f"从站地址不匹配：收到0x{resp[0]:02X}，预期0x{self.address:02X}")
                return None
            if resp[1] != 0x03:
                print(f"功能码错误：收到0x{resp[1]:02X}，预期0x03")
                return None
            if resp[2] != 0x04:  # 数据长度固定为4字节（2个寄存器）
                print(f"数据长度错误：收到0x{resp[2]:02X}，预期0x04")
                return None

            # 5. 解析距离数据（协议示例：00 00 B8 47 → 十进制47175 → 47.175mm）
            # 数据格式：4字节（前2字节高位，后2字节低位，组合为32位整数）
            data_bytes = resp[3:7]  # 提取数据部分（4字节）
            raw_value = int.from_bytes(data_bytes, byteorder='big')  # 大端模式解析
            distance = raw_value * self.resolution  # 转换为mm（1μm = 0.001mm）

            # 6. 量程过滤（根据当前量程类型校验）
            if (self.range_type == 50 and not (0 <= distance <= 50)) or \
               (self.range_type == 100 and not (0 <= distance <= 100)):
                print(f"距离超出{self.range_type}mm量程：{distance:.3f}mm")
                return None

            return round(distance, 3)  # 保留3位小数（符合1μm分辨率）

        except Exception as e:
            print(f"读取距离失败：{str(e)}")
            return None

    def close(self):
        pass

def check_sensor_switch():
    """根据当前测量值切换50mm/100mm传感器（阈值20mm）"""
    if not state.current_sensor:
        state.current_sensor = state.sensor_50  # 初始使用50mm传感器
        return
    
    distance = state.current_sensor.read_distance()
    if distance is None:
        return  # 数据无效时不切换
    
    # 根据当前量程和测量值切换
    if state.current_sensor.range_type == 50:
        if distance > 20:  # 50mm量程超过20mm切换到100mm
            state.current_sensor = state.sensor_100
            print(f"切换到100mm量程传感器（当前值：{distance:.3f}mm）")
    else:  # 当前为100mm量程
        if distance < 20:  # 100mm量程低于20mm切换到50mm
            state.current_sensor = state.sensor_50
            print(f"切换到50mm量程传感器（当前值：{distance:.3f}mm）")

def get_current_sensor_range():
    return state.current_sensor.range_type if state.current_sensor else "未知"

# ---------- 数据收集器 ----------
class DataCollector:
    def __init__(self, motor, sensor):
        self.running = False
        self.thread = None
        self.data = []
        self.motor = motor
        self.sensor = sensor

    def start(self, interval=0.05):
        if self.running:
            return
        self.data = []
        self.running = True
        self.thread = threading.Thread(target=self._collect_loop, args=(interval,), daemon=True)
        self.thread.start()

    def _collect_loop(self, interval):
        while self.running and state.running:
            current_angle = self.motor.get_current_angle()
            distance = self.sensor.read_distance()
            if current_angle is not None and distance is not None:
                normalized_angle = current_angle % 360
                current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
                with state.lock:
                    state.current_angle = normalized_angle
                    state.current_distance = distance
                    state.data.append((current_time, state.total_move_distance, normalized_angle, distance))
                self.data.append((current_time, state.total_move_distance, normalized_angle, distance))
            time.sleep(interval)

    def stop(self):
        self.running = False
        if self.thread:
            self.thread.join(timeout=2)

    def append_to_csv(self):
        try:
            file_exists = os.path.isfile(state.csv_file)
            with open(state.csv_file, 'a', newline='', encoding='utf-8') as f:
                writer = csv.writer(f)
                if not file_exists:
                    writer.writerow([
                        "测量时间", "移动距离(mm)", "云台角度(°)",
                        "距离(mm)", "传感器量程", "采集模式"
                    ])
                for row in self.data:
                    writer.writerow(list(row) + [get_current_sensor_range(), "连续"])
            return True
        except Exception as e:
            print(f"连续数据保存失败: {e}")
            return False

# ---------- 主测量流程 ----------
def run_measurement():
    try:
        with state.lock:
            if not state.serial_port or not state.serial_port.is_open:
                state.system_status = "未连接串口"
                print("未连接串口")
                return False

        serial_lock = threading.Lock()
        photo_controller = PhotoelectricSwitchController(state.serial_port, slave_address=state.config.get("photo_address", 6), lock=serial_lock)
        stepper = StepperMotor(state.serial_port, motor_id=state.config["stepper_id"], lock=serial_lock, photo_controller=photo_controller)
        motor = HaitaiMotor(state.serial_port, motor_id=state.config["motor_id"], lock=serial_lock)
        # 初始化两个量程的传感器（从配置文件读取地址）
        state.sensor_50 = LE100485Sensor(
            state.serial_port,
            state.config["sensor_50_address"],  # 50mm传感器地址（如0x01）
            50,  # 量程50mm
            serial_lock
        )
        state.sensor_100 = LE100485Sensor(
            state.serial_port,
            state.config["sensor_100_address"],  # 100mm传感器地址（如0x02）
            100,  # 量程100mm
            serial_lock
        )
        state.current_sensor = state.sensor_50  # 默认使用50mm传感器

        with state.lock:
            state.stepper = stepper
            state.motor = motor
            state.measuring = True
            state.system_status = "测量中"
            state.csv_file = f"测量数据_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"

        print("读取光电开关初始状态...")
        photo_controller.update_all_switches()
        print("光电开关状态:", state.photo_switch)

        print("移动到负限位...")
        # 使用分段移动（例如 50mm 每段），直到限位或完成
        if not stepper.move_distance(1000, 'backward', segment_mm=50, segment_timeout_per_mm=0.03, pr_speed=0x0064):
            with state.lock:
                state.system_status = "移动到负限位失败"
            return False

        # print("移动到原点等待开关...")
        # if not photo_controller.wait_for_switch("origin", timeout=60):
        #     with state.lock:
        #         state.system_status = "未检测到原点信号"
        #     return False
        # print("已到达原点")

        for i in range(state.config["repeat_times"]):
            with state.lock:
                if not state.running:
                    break
                state.system_status = f"测量中 (第{i+1}/{state.config['repeat_times']}轮)"

            print(f"第{i+1}轮：移动10mm...")
            if not stepper.move_distance(10, 'forward', segment_mm=10, segment_timeout_per_mm=0.02, pr_speed=0x0064):
                with state.lock:
                    state.system_status = f"第{i+1}轮移动失败"
                return False
            with state.lock:
                state.total_move_distance += 10

            check_sensor_switch()

            if state.config["measure_mode"] == "continuous":
                collector = DataCollector(motor, state.current_sensor)
                collector.start(interval=state.config["sample_interval"])
                if not motor.rotate_360_continuous():
                    collector.stop()
                    with state.lock:
                        state.system_status = f"第{i+1}轮旋转失败"
                    return False
                collector.stop()
                collector.append_to_csv()
            else:
                if not motor.rotate_fixed_angle(state.config["angle_step"]):
                    with state.lock:
                        state.system_status = f"第{i+1}轮定点采集失败"
                    return False

            motor.move_to_zero()

        motor.close_motor()
        with state.lock:
            state.system_status = "测量完成"
            state.measuring = False
        print("所有测量任务完成！")
        return True

    except Exception as e:
        traceback.print_exc()
        with state.lock:
            state.system_status = f"测量异常: {str(e)}"
            state.measuring = False
        print(f"测量出错: {e}")
        return False
    finally:
        with state.lock:
            state.running = False

# ---------- API ----------
@app.route('/api/connect', methods=['POST'])
def connect_device():
    try:
        config = request.json
        if not config:
            return jsonify({"status": "error", "message": "请提供配置参数"}), 400

        with state.lock:
            state.config.update(config)
            if state.serial_port and state.serial_port.is_open:
                state.serial_port.close()

            state.serial_port = serial.Serial(
                port=state.config["port"],
                baudrate=state.config["baudrate"],
                parity=serial.PARITY_NONE,
                stopbits=serial.STOPBITS_ONE,
                bytesize=serial.EIGHTBITS,
                timeout=1,
                write_timeout=5
            )
            try:
                if hasattr(serial, "rs485"):
                    rs485_settings = serial.rs485.RS485Settings(
                        rts_level_for_tx=True,
                        rts_level_for_rx=False,
                        delay_before_tx=0.002,
                        delay_before_rx=0.002
                    )
                    state.serial_port.rs485_mode = rs485_settings
            except Exception:
                pass
            state.system_status = "已连接"

        return jsonify({"status": "success", "message": "设备连接成功", "config": state.config})
    except serial.SerialException as e:
        msg = f"串口连接失败: {str(e)}"
        with state.lock:
            state.system_status = "连接失败"
        return jsonify({"status": "error", "message": msg}), 500
    except Exception as e:
        traceback.print_exc()
        with state.lock:
            state.system_status = "连接失败"
        return jsonify({"status": "error", "message": f"连接异常: {str(e)}"}), 500

@app.route('/api/disconnect', methods=['POST'])
def disconnect_device():
    with state.lock:
        if state.serial_port and state.serial_port.is_open:
            state.serial_port.close()
        state.system_status = "已断开连接"
        state.running = False
        state.measuring = False
    return jsonify({"status": "success", "message": "已断开连接"})

@app.route('/api/start', methods=['POST'])
def start_measure():
    with state.lock:
        if state.system_status not in ("已连接",):
            return jsonify({"status": "error", "message": "请先连接设备"}), 400
        if state.running:
            return jsonify({"status": "error", "message": "测量已在进行中"}), 400
        state.running = True
        threading.Thread(target=run_measurement, daemon=True).start()
    return jsonify({"status": "success", "message": "测量已启动"})

@app.route('/api/stop', methods=['POST'])
def stop_measure():
    with state.lock:
        if not state.running:
            return jsonify({"status": "error", "message": "测量未在进行中"}), 400
        state.running = False
        state.measuring = False
        state.system_status = "待机"
    return jsonify({"status": "success", "message": "测量已停止"})

@app.route('/api/emergency', methods=['POST'])
def emergency_stop():
    with state.lock:
        state.running = False
        state.measuring = False
        if state.stepper:
            try:
                state.stepper.emergency_stop()
            except Exception as e:
                print(f"步进急停失败: {e}")
        if state.motor:
            try:
                state.motor.close_motor()
            except Exception as e:
                print(f"云台关闭失败: {e}")
        state.system_status = "已急停"
    return jsonify({"status": "success", "message": "已执行紧急停止"})

@app.route('/api/reset', methods=['POST'])
def reset_system():
    with state.lock:
        state.running = False
        state.measuring = False
        state.data = []
        state.current_angle = None
        state.current_distance = None
        state.total_move_distance = 0
        if state.motor:
            try:
                state.motor.move_to_zero()
            except Exception as e:
                print(f"复位失败: {e}")
        state.system_status = "待机"
    return jsonify({"status": "success", "message": "系统已复位"})

@app.route('/api/status', methods=['GET'])
def get_status():
    with state.lock:
        return jsonify({
            "system_status": state.system_status,
            "measuring": state.measuring,
            "current_angle": round(state.current_angle, 4) if state.current_angle is not None else None,
            "current_distance": round(state.current_distance, 5) if state.current_distance is not None else None,
            "total_move_distance": round(state.total_move_distance, 1),
            "data_count": len(state.data),
            "photo_switch": state.photo_switch,
            "current_sensor_range": get_current_sensor_range()
        })

@app.route('/api/config', methods=['GET'])
def get_config():
    with state.lock:
        return jsonify({"status": "success", "config": state.config})

@app.route('/api/set-mode', methods=['POST'])
def set_measure_mode():
    try:
        data = request.json
        if not data or "measure_mode" not in data:
            return jsonify({"status": "error", "message": "请指定采集模式"}), 400
        with state.lock:
            state.config["measure_mode"] = data["measure_mode"]
            if data["measure_mode"] == "fixed_angle":
                if "angle_step" not in data or not (1 <= data["angle_step"] <= 180):
                    return jsonify({"status": "error", "message": "角度间隔必须为1-180之间的整数"}), 400
                state.config["angle_step"] = data["angle_step"]
        return jsonify({"status": "success", "message": f"已设置为{data['measure_mode']}模式", "config": {"measure_mode": state.config["measure_mode"], "angle_step": state.config["angle_step"]}})
    except Exception as e:
        traceback.print_exc()
        return jsonify({"status": "error", "message": f"设置失败: {str(e)}"}), 500

@app.route('/api/download-data', methods=['GET'])
def download_data():
    with state.lock:
        if not state.csv_file or not os.path.exists(state.csv_file):
            return jsonify({"status": "error", "message": "无数据文件"}), 404
        return send_file(state.csv_file, as_attachment=True)

if __name__ == '__main__':
    print("=== 激光位移测量系统 后端 ===")
    print(f"启动时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print("API服务地址: http://0.0.0.0:5000")
    app.run(host='0.0.0.0', port=5000, debug=False, use_reloader=False)